package cc.owoo.godpen.network;

import cc.owoo.godpen.thread.Threads;

import java.io.IOException;
import java.util.concurrent.CountDownLatch;

/**
 * 心跳socket
 * Created by nimensei
 * 2022-05-13 下午 07:16
 **/
public class HeartbeatSocket {
    private static final String HEARTBEAT_MESSAGE = "put";// 心跳消息
    private static final String TRY_FINISH_MESSAGE = "try";// 尝试正常关闭
    private static final String FINISH_MESSAGE = "end";// 正常关闭消息

    private final Object lock = new Object();// 线程锁
    private final TalkbackSocket talkbackSocket;// 对话socket
    private final int heartbeatDelay;// 心跳延迟
    private int launch;// 启动模式，1: 主, 2: 从
    private int state;// 运行状态，0: 未结束, 1: 异常关闭, 2: 正常结束
    private boolean isDownFinish;// 是否在下一条语句结束
    private Runnable onClose;// 断开连接监听器
    private Runnable onFinish;// 是否正常结束
    private CountDownLatch countDownLatch;// 线程等待锁

    /**
     * 创建心跳socket
     *
     * @param talkbackSocket 对话socket
     * @param heartbeatDelay 每一次心跳的间隔，值越小socket关闭的速度越快，但不能小于1，且从模式下不生效
     */
    public HeartbeatSocket(TalkbackSocket talkbackSocket, int heartbeatDelay) {
        if (heartbeatDelay < 1)
            throw new IllegalArgumentException("心跳延迟不能小于1");
        this.talkbackSocket = talkbackSocket;
        this.heartbeatDelay = heartbeatDelay;
    }

    /**
     * 启动主模式，socket的接收方需要启动从模式，主模式拥有更快的关闭速度
     * <p>
     * 该函数不会在线程中运行，在心跳没有失效的情况下不会退出
     */
    public void master() {
        synchronized (lock) {
            if (launch != 0 || state != 0)
                return;
            launch = 1;
        }
        try {
            talkbackSocket.send(HEARTBEAT_MESSAGE);
        } catch (IOException e) {
            onClose();
            return;
        }
        heartbeat(true);
    }

    /**
     * 启动从模式
     * 该函数不会在线程中运行，在心跳没有失效的情况下不会退出
     */
    public void slave() {
        synchronized (lock) {
            if (launch != 0 || state != 0)
                return;
            launch = 2;
        }
        heartbeat(false);
    }

    /**
     * 开始执行心跳
     */
    private void heartbeat(boolean delay) {
        while (true) {
            try {
                String text = talkbackSocket.acceptText();
                synchronized (lock) {
                    if (text == null)
                        break;
                    if (text.equals(FINISH_MESSAGE)) {
                        onFinish();
                        return;
                    }
                    if (isDownFinish)
                        break;
                    if (text.equals(TRY_FINISH_MESSAGE)) {
                        talkbackSocket.send(FINISH_MESSAGE);
                        state = 2;
                        onFinish();
                        return;
                    }
                    if (state == 2) {
                        isDownFinish = true;
                        continue;
                    }
                    if (!text.equals(HEARTBEAT_MESSAGE))
                        break;
                    talkbackSocket.send(HEARTBEAT_MESSAGE);
                }
            } catch (IOException e) {
                break;
            }
            if (!delay)
                continue;
            long target = System.currentTimeMillis() + heartbeatDelay;
            while (System.currentTimeMillis() < target) {
                Threads.delay(1);
                if (state == 1)
                    return;
                if (state == 2)
                    break;
            }
        }
        onClose();
    }

    /**
     * 心跳包发送过程中出现了异常，结束该心跳类
     */
    private void onClose() {
        synchronized (lock) {
            if (state == 1)
                return;
            state = 1;
            closeSocket();
            if (onClose != null)
                onClose.run();
        }
    }

    /**
     * 正常关闭
     */
    private void onFinish() {
        synchronized (lock) {
            if (state != 2)
                return;
            state = 3;
            if (countDownLatch != null)
                countDownLatch.countDown();
            if (onFinish != null)
                onFinish.run();
        }
    }

    /**
     * 完成心跳的发送，并等待结束
     */
    public boolean finish() {
        synchronized (lock) {
            if (state == 2)
                return true;
            if (state != 0)
                return false;
            state = 2;
            countDownLatch = new CountDownLatch(1);
            try {
                talkbackSocket.send(TRY_FINISH_MESSAGE);
            } catch (IOException e) {
                state = 0;
                onClose();
                return false;
            }
        }
        try {
            countDownLatch.await();
        } catch (InterruptedException ignored) {
        }
        return true;
    }

    /**
     * 手动关闭连接
     */
    public void close() {
        synchronized (lock) {
            onClose();
        }
    }

    /**
     * 设置断开连接监听器，该函数会在心跳失效时被调用
     *
     * @param onClose 断开连接监听器
     */
    public void setOnClose(Runnable onClose) {
        synchronized (lock) {
            this.onClose = onClose;
            if (state == 1)
                onClose.run();
        }
    }

    /**
     * 获取断开连接监听器
     *
     * @return 断开连接监听器
     */
    public Runnable getOnClose() {
        return onClose;
    }

    /**
     * 设置正常结束监听器，该函数会在心跳正常结束时被调用
     *
     * @param onFinish 正常结束监听器
     */
    public void setOnFinish(Runnable onFinish) {
        synchronized (lock) {
            this.onFinish = onFinish;
            if (state == 2)
                onFinish.run();
        }
    }

    /**
     * 获取正常结束监听器
     *
     * @return 正常结束监听器
     */
    public Runnable getOnFinish() {
        return onFinish;
    }

    /**
     * 关闭Socket
     */
    private void closeSocket() {
        try {
            if (!talkbackSocket.getSocket().isClosed())
                talkbackSocket.close();
        } catch (IOException e) {
        }
    }

    /**
     * 获取对话socket
     *
     * @return 对话socket
     */
    public TalkbackSocket getTalkbackSocket() {
        return talkbackSocket;
    }

    public enum Type {
        MASTER,// 主模式
        SLAVE// 从模式
    }
}
